package com.nulstudio.emotion.text.config;

import com.nulstudio.emotion.text.mapper.AccountMapper;
import com.nulstudio.emotion.text.mapper.AuthorityMapper;
import com.nulstudio.emotion.text.service.AuthorityService;
import com.nulstudio.emotion.text.entity.NulUserDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

/**
 * Security configuration for this application, using Spring Security.
 * Method security is enabled.
 * @author nullsty
 * @since 2.0
 */
@Configuration
@EnableWebSecurity @EnableMethodSecurity
public class NulSecurityConfig {

    /**
     * Mapper for account module.
     */
    private final AccountMapper accountMapper;

    /**
     * Mapper for authority module.
     */
    private final AuthorityMapper authorityMapper;

    /**
     * Authentication exception handler for security module.
     */
    private final NulAuthenticationEntryPoint authenticationEntryPoint;

    /**
     * Inject mappers for security.
     * @param accountMapper mapper for account module
     * @param authorityMapper mapper for authentication module
     * @param authenticationEntryPoint authentication exception handler for security module
     */
    protected NulSecurityConfig(AccountMapper accountMapper, AuthorityMapper authorityMapper,
                             NulAuthenticationEntryPoint authenticationEntryPoint) {
        this.accountMapper = accountMapper;
        this.authorityMapper = authorityMapper;
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    /**
     * Inject a new {@code NulJwtTokenFilter} for JWT authentication.
     * @return an instance of {@code NulJwtTokenFilter}
     */
    @Bean
    protected NulJwtTokenFilter jwtTokenFilter() {
        return new NulJwtTokenFilter();
    }

    /**
     * Configure Spring Security filter chain.
     * @param http http security configuration
     * @return an instance of {@code SecurityFilterChain}
     * @throws Exception when internal errors in Spring Security occurred
     */
    @Bean
    protected SecurityFilterChain filterChain(@NonNull HttpSecurity http) throws Exception {
        // Configure Spring Security filter chain.
        http
                // Disable CSRF configurations.
                .csrf(AbstractHttpConfigurer::disable)

                // Disable session states.
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

                // Configure paths to authenticate.
                .authorizeHttpRequests(authorize ->
                        authorize.anyRequest().permitAll()
                )

                // Exception handling logic for security.
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()

                // CORS Configuration.
                .cors().configurationSource(corsConfigurationSource()).and()

                // Add a filter to validate JWT token.
                .addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }


    /**
     * Provide authority list for Spring Security.
     * @param auth authentication manager to provide data
     * @throws Exception when internal errors in Spring Security occurred
     */
    @Autowired
    protected void configureGlobal(@NonNull AuthenticationManagerBuilder auth) throws Exception {
        // Inject mappers manually.
        final AuthorityService authorityService = new AuthorityService(authorityMapper, accountMapper);

        // Set user detail service for method authentication.
        auth.userDetailsService(authorityService).passwordEncoder(new BCryptPasswordEncoder());
    }

    /**
     * Get CORS Configuration.
     * @return CORS Configuration
     */
    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOrigins(List.of("*"));
        corsConfiguration.setAllowedMethods(List.of("GET", "POST"));
        corsConfiguration.setAllowedHeaders(List.of("*"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    /**
     * Get user detail of current user.
     * @return user detail of current user
     */
    @NonNull
    public static NulUserDetail getContextUserDetail() {
        return (NulUserDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}
